﻿using System;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;

namespace TibiaMC
{
    public static class MC
    {
        #region Variables
        private const string TibiaClassName = "TibiaClient";
        #endregion

        #region Methods
        /// <summary>
        /// Open a new MC, using the path from the process parameter
        /// </summary>
        /// <param name="process">The process to obtain the path</param>
        /// <returns>The new MC process</returns>
        /// <exception cref="System.NotSupportedException"/>
        /// <exception cref="System.PlatformNotSupportedException"/>
        /// <exception cref="System.InvalidOperationException"/>
        public static Process Open(Process process)
        {
            ProcessModule mainModule = process.MainModule;
            try
            {
                return Open(mainModule.FileName);
            }
            finally
            {
                mainModule.Dispose();
            }
        }
        /// <summary>
        /// Open a new MC, using the path parameter
        /// </summary>
        /// <param name="path">The path to the tibia executable</param>
        /// <returns>The new MC process</returns>
        /// <exception cref="System.ComponentModel.Win32Exception">Underlying native Windows exceptions</exception>
        /// <exception cref="System.IO.FileNotFoundException">File specified by 'path' not found</exception>
        public static Process Open(string path)
        {
            if (!File.Exists(path))
                throw new FileNotFoundException();

            if (!NativeMethods.EnumWindows(new NativeMethods.EnumWindowsProc(EnumWindowsProcedure), NativeMethods.NULL))
            {
                int lastError = Marshal.GetLastWin32Error();
                throw new Win32Exception(lastError);
            }

            return RunClient(path);
        }
        #endregion

        #region Private Methods
        private static string TibiaMutexName(int processId)
        {
            int sessionId;
            if (!NativeMethods.ProcessIdToSessionId(processId, out sessionId))
            {
                int lastError = Marshal.GetLastWin32Error();
                throw new Win32Exception(lastError);
            }

            return string.Concat("\\Sessions\\", sessionId, "\\BaseNamedObjects\\TibiaPlayerMutex");
        }
        private static bool EnumWindowsProcedure(IntPtr hwnd, IntPtr lparam)
        {
            StringBuilder stringBuilder = new StringBuilder(TibiaClassName.Length + 1);
            if (NativeMethods.GetClassName(hwnd, stringBuilder, stringBuilder.Capacity) == 0)
            {
                int lastError = Marshal.GetLastWin32Error();
                throw new Win32Exception(lastError);
            }

            if (stringBuilder.ToString() == TibiaClassName)
            {
                int processId;
                if (NativeMethods.GetWindowThreadProcessId(hwnd, out processId) == 0)
                {
                    int lastError = Marshal.GetLastWin32Error();
                    throw new Win32Exception(lastError);
                }
                
                IntPtr processHandle = NativeMethods.OpenProcess(NativeMethods.ProcessAccessFlags.DupHandle, false, processId);
                if (processHandle == NativeMethods.INVALID_HANDLE_VALUE)
                {
                    int lastError = Marshal.GetLastWin32Error();
                    throw new Win32Exception(lastError);
                }

                try
                {
                    string tibiaMutexName = TibiaMutexName(processId);
                    foreach (SystemHandle systemHandle in SystemHandle.EnumerateProcessHandles(processId))
                    {
                        if (tibiaMutexName != systemHandle.Name)
                            continue;

                        systemHandle.Close(processHandle);
                    }
                }
                finally
                {
                    NativeMethods.CloseHandle(processHandle);
                }
            }

            return true;
        }
        private static Process RunClient(string path)
        {
            ProcessStartInfo processStartInfo = new ProcessStartInfo(path);
            processStartInfo.UseShellExecute = true;
            processStartInfo.WorkingDirectory = Path.GetDirectoryName(path);

            return Process.Start(processStartInfo);
        }
        #endregion
    }
}
